/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package maxbe.rel2xml.generator;

import static maxbe.rel2xml.util.Config.HIERARCHICAL_RESULT;
import static maxbe.rel2xml.util.Config.IMPLICIT_FKEYS;
import static maxbe.rel2xml.util.Config.MAP_ATTRIBUTES;
import static maxbe.rel2xml.util.Config.MAP_ELEMENTS;
import static maxbe.rel2xml.util.Config.MAP_UNCHECKED;
import static maxbe.rel2xml.util.Config.OMIT_NULLS;
import static maxbe.rel2xml.util.Config.PRESERVE_ENTITIES;
import static maxbe.rel2xml.util.Config.PRETTY_PRINT;
import static maxbe.rel2xml.util.Config.SPLIT_UPDATES;
import static maxbe.rel2xml.util.Config.VERBOSE;
import static maxbe.rel2xml.util.Config.setBoolean;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

/**
 * Main class to be called
 * 
 * @author Max Bechtold
 * 
 */
public class Main {

	private static Logger log = Logger.getLogger(Main.class);

	private static Options options;

	private static CommandLine cl;

	private static String dbName;

	private static boolean dryRun = false;

	static {
		PropertyConfigurator.configure("log4j.properties");

		setBoolean(OMIT_NULLS, true);

		options = new Options();

		// General options
		options.addOption("v", "verbose", false,
				"Print stacktraces on exceptions");
		options.addOption("h", "help", false,
				"Displays the lines you are reading right now");

		// Document generation related options
		options.addOption("d", "ddlfile", true,
				"File containing ddl instructions (<schema>-ddl.txt)");
		options.addOption(
				"f",
				"fillfile",
				true,
				"File containing fill instructions. If unspecified, all files matching <schema>-fil<number>.txt will be used");
		options.addOption(
				"m",
				"mapfile",
				true,
				"File containing mapping instructions. If unspecified, all files matching <schema>-map<number>.txt will be used");
		options.addOption(
				"o",
				"omit-implicit-keys",
				false,
				"Omit implicit foreign keys when mapping hierarchically. Note: You are required to adapt predicates on those implicit"
						+ " foreign keys manually!");

		options.addOption(
				"p",
				"pretty-print",
				false,
				"Nicely format generated documents (indentation and line wrapping known as 'pretty print')");
		options.addOption("e", "elements-default", false,
				"If specified, maps columns without defined mapping as elements");
		options.addOption("a", "attributes-default", false,
				"If specified, maps columns without defined mapping as attributes");
		options.addOption(
				"u",
				"unchecked-mapping",
				false,
				"This will make the program accept mapping hierarchies without dependent foreign key relationships "
						+ "(unclear semantics, use with care)");

		// Query translation related options
		options.addOption(
				"q",
				"queryfile",
				true,
				"File containing queries to translate (no automatic scanning as with fill and map files)");
		options.addOption(
				"dr",
				"dry-run",
				false,
				"No document generation, only translation of queries (if applicable). This still requires dll, fill, and map files. "
						+ "Be careful only to use translated queries with documents generated from the same files!");
		options.addOption(
				"pr",
				"preserve-entities",
				false,
				"This option results in preserving entities in query results. Otherwise, all selected columns will be "
						+ "part of a single element");
		options.addOption("hr", "hierarchical-result", false,
				"Will result in attempt to establish entity hierarchy for result output");
		options.addOption(
				"s",
				"split-updates",
				false,
				"Update statements assigning more than one column will result in multiple replace-value-of-node-expressions"
						+ " instead of a single replace-node-expression");

	}

	public static void main(String[] args) {
		log.info("Starting up...");

		parseArguments(args);
		run();

		log.info("Exiting.");
	}

	private static void parseArguments(String[] args) {
		if (args.length == 0) {
			System.out
					.println("Please call Main according to following usage rules:");
			HelpFormatter helpFormatter = new HelpFormatter();
			helpFormatter.printHelp("Main", options);
			System.exit(0);
		}

		CommandLineParser clp = new PosixParser();

		try {
			cl = clp.parse(options, args);

			if (!cl.hasOption('d') || cl.hasOption('h')) {
				HelpFormatter helpFormatter = new HelpFormatter();
				helpFormatter.printHelp("Main", options);
				System.exit(0);
			}

			if (cl.hasOption('v'))
				setBoolean(VERBOSE, true);

			if (cl.hasOption('o'))
				setBoolean(IMPLICIT_FKEYS, true);

			if (cl.hasOption('p'))
				setBoolean(PRETTY_PRINT, true);

			if (cl.hasOption('a'))
				setBoolean(MAP_ATTRIBUTES, true);

			if (cl.hasOption('e'))
				if (cl.hasOption('a'))
					log.error(String
							.format("Cannot set options 'a' and 'e' at the same time, ignoring 'e'."));
				else
					setBoolean(MAP_ELEMENTS, true);

			if (cl.hasOption('u'))
				setBoolean(MAP_UNCHECKED, true);

			if (cl.hasOption("pr"))
				setBoolean(PRESERVE_ENTITIES, true);

			if (cl.hasOption("hr"))
				setBoolean(HIERARCHICAL_RESULT, true);

			if (cl.hasOption('s'))
				setBoolean(SPLIT_UPDATES, true);

			if (cl.hasOption("dr"))
				dryRun = true;

		}

		catch (ParseException e) {
			log.error("Error while parsing arguments.");
			log.error(e);
			System.exit(1);
		}
	}

	private static void run() {
		String ddlFileName = cl.getOptionValue('d');
		File ddlFile = new File(ddlFileName);
		dbName = ddlFile.getName().substring(0,
				ddlFile.getName().indexOf("-ddl"));
		File workingDir = ddlFile.getParentFile();

		File queryFile = null;

		if (cl.hasOption('q'))
			queryFile = new File(cl.getOptionValue('q'));

		if (cl.hasOption('f') && cl.hasOption('m')) {
			// Run single document generation
			log.info("Initializing XGenerator...");
			File fillFile = new File(cl.getOptionValue('f'));
			File mapFile = new File(cl.getOptionValue('m'));

			File dir = new File(workingDir, dbName);

			XGenerator generator = new XGenerator(ddlFile, fillFile, mapFile,
					queryFile, dir, dbName);
			boolean green = generator.generate(dryRun);

			if (green && cl.hasOption('q'))
				generator.translate();
		} else {
			// Scan for fill and map files
			List<File> fillFiles = null;
			List<File> mapFiles = null;

			if (cl.hasOption('f')) {
				fillFiles = new ArrayList<File>(1);
				fillFiles.add(new File(cl.getOptionValue('f')));
			} else {
				FileFilter fillFileFilter = new FillFileFilter(dbName);
				fillFiles = Arrays.asList(workingDir.listFiles(fillFileFilter));

				if (fillFiles.size() == 0) {
					log.error(String.format(
							"No fillFile found for ddlFile '%s'", ddlFileName));
					System.exit(1);
				}
			}

			if (cl.hasOption('m')) {
				mapFiles = new ArrayList<File>(1);
				mapFiles.add(new File(cl.getOptionValue('m')));
			} else {
				MapFileFilter mapFileFilter = new MapFileFilter(dbName);
				mapFiles = Arrays.asList(workingDir.listFiles(mapFileFilter));
			}

			log.info(String.format(
					"Found %d fillFile(s) and %d mapFile(s) for ddlFile '%s'",
					fillFiles.size(), mapFiles.size(), ddlFileName));

			// Generate documents for all combinations
			int i = 1;
			for (File fillFile : fillFiles) {
				Iterator<File> it = mapFiles.iterator();
				File mapFile = (it.hasNext() ? it.next() : null);

				do {
					// Generate once if no mapFiles existent
					log.info(String
							.format("Initializing XGenerator for combination #%d...",
									i));
					File dir = new File(workingDir, dbName + '#' + i++);
					XGenerator generator = new XGenerator(ddlFile, fillFile,
							mapFile, queryFile, dir, dbName);
					boolean green = generator.generate(dryRun);

					if (green && cl.hasOption('q'))
						generator.translate();
				} while (it.hasNext() && (mapFile = it.next()) != null);
			}
		}
	}

	static class FillFileFilter implements FileFilter {

		private String prefix;

		@Override
		public boolean accept(File pathname) {
			return pathname.getName().matches(prefix + "-fil(\\d)?\\.txt");
		}

		public FillFileFilter(String prefix) {
			this.prefix = prefix;
		}
	}

	static class MapFileFilter implements FileFilter {

		private String prefix;

		@Override
		public boolean accept(File pathname) {
			return pathname.getName().matches(prefix + "-map(\\d)?\\.txt");
		}

		public MapFileFilter(String prefix) {
			this.prefix = prefix;
		}
	}
}
